#region Copyright Notices
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
// 
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#endregion

using System;
using System.IO;
using System.Collections.Generic;
using System.Globalization;

namespace Yarr
{
	/// <summary>
	/// Yarr primitives and utilities
	/// </summary>
    internal static class Primitives
    {
        #region Primitives

        internal static bool Eq(Cons args)
        {
            if (args == null)
                return false;

            object last = args.First();

            foreach (object item in (Cons)args.Rest())
            {
                if (!Eq(last, item))
                    return false;
                last = item;
            }
            return true;
        }

        internal static bool Eq(object x, object y)
        {
            if (x == null || y == null)
                return x == y;

            if (IsValueType(x) && IsValueType(y))
                return Eql(x, y);

            return object.ReferenceEquals(x, y);
        }

        internal static bool Eql(Cons args)
        {
            if (args == null)
                return false;

            object last = args.First();

            foreach (object item in (Cons)args.Rest())
            {
                if (!Eql(last, item))
                    return false;
                last = item;
            }
            return true;
        }

        internal static bool Eql(object x, object y) 
		{
			
			if ( x == null || y == null)
				return x == y;

            if (IsNumber(x) && IsNumber(y))
                return NumberEql(x, y);

			return (x.Equals(y));
			
		}

        internal static bool IsAtom(Object x)
        {
            if (x == null || (x is Symbol) || (x is Delegate))
                return true;

            return Type.GetTypeCode(x.GetType()) != TypeCode.Object;
        }

        internal static bool IsList(Object x)
        {
            if (x == null)
                return true;
            else
                return (x.GetType() == typeof(Cons));
        }

        internal static bool IsNull(Object arg)
        {
            return (arg == null);
        }

        internal static bool IsNumber(Object x)
        {
            return IsNumber(Type.GetTypeCode(x.GetType()));
        }

        internal static bool IsNumber(TypeCode t)
        {
            switch (t)
            {
                case TypeCode.Char:
                case TypeCode.SByte:
                case TypeCode.Byte:
                case TypeCode.Int16:
                case TypeCode.UInt16:
                case TypeCode.Int32:
                case TypeCode.UInt32:
                case TypeCode.Int64:
                case TypeCode.UInt64:
                case TypeCode.Decimal:
                case TypeCode.Single:
                case TypeCode.Double:
                    return true;

                default:
                    return false;
            }
        }

        internal static bool IsTypeOf(Object type, Object instance)
        {
            if (type == null)
                throw new YarrException("IsTypeOf: Specified type is null.");

            if (type is Type)
            {
                return ((Type)type).IsInstanceOfType(instance);
            }

            throw new YarrException(String.Format("IsTypeOf: Expecting a type, but received {0}", type.ToString()));
        }

        internal static bool IsValueType(Object x)
        {
            switch (Type.GetTypeCode(x.GetType()))
            {
                case TypeCode.Object:
                case TypeCode.Empty:
                    return false;

                default:
                    return true;
            }
        }

        internal static bool NumberEql(object a, object b)
        {
            try
            {
                TypeCode aType = Type.GetTypeCode(a.GetType());
                TypeCode bType = Type.GetTypeCode(b.GetType());
                if (aType == TypeCode.Double || bType == TypeCode.Double)
                    return (Double)Convert.ChangeType(a, typeof(Double)) == (Double)Convert.ChangeType(b, typeof(Double));

                else if (aType == TypeCode.Decimal || bType == TypeCode.Decimal)
                    return (Decimal)Convert.ChangeType(a, typeof(Decimal)) == (Decimal)Convert.ChangeType(b, typeof(Decimal));

                else if (aType == TypeCode.Single || bType == TypeCode.Single)
                    return (Single)Convert.ChangeType(a, typeof(Single)) == (Single)Convert.ChangeType(b, typeof(Single));

                else if (aType == TypeCode.UInt64 || bType == TypeCode.UInt64)
                    return (UInt64)Convert.ChangeType(a, typeof(UInt64)) == (UInt64)Convert.ChangeType(b, typeof(UInt64));

                else if (aType == TypeCode.Int64 || bType == TypeCode.Int64)
                    return (Int64)Convert.ChangeType(a, typeof(Int64)) == (Int64)Convert.ChangeType(b, typeof(Int64));

                else if (aType == TypeCode.UInt32 || bType == TypeCode.UInt32)
                    return (UInt32)Convert.ChangeType(a, typeof(UInt32)) == (UInt32)Convert.ChangeType(b, typeof(UInt32));

                else if (aType == TypeCode.Int32 || bType == TypeCode.Int32)
                    return (Int32)Convert.ChangeType(a, typeof(Int32)) == (Int32)Convert.ChangeType(b, typeof(Int32));

                else if (aType == TypeCode.UInt16 || bType == TypeCode.UInt16)
                    return (UInt16)Convert.ChangeType(a, typeof(UInt16)) == (UInt16)Convert.ChangeType(b, typeof(UInt16));

                else if (aType == TypeCode.Char || bType == TypeCode.Char)
                    return (Char)Convert.ChangeType(a, typeof(Char)) == (Char)Convert.ChangeType(b, typeof(Char));

                else if (aType == TypeCode.Int16 || bType == TypeCode.Int16)
                    return (Int16)Convert.ChangeType(a, typeof(Int16)) == (Int16)Convert.ChangeType(b, typeof(Int16));

                else if (aType == TypeCode.Byte || bType == TypeCode.Byte)
                    return (Byte)Convert.ChangeType(a, typeof(Byte)) == (Byte)Convert.ChangeType(b, typeof(Byte));

                else if (aType == TypeCode.SByte || bType == TypeCode.SByte)
                    return (SByte)Convert.ChangeType(a, typeof(SByte)) == (SByte)Convert.ChangeType(b, typeof(SByte));
                else
                    throw new YarrException("NumberEql: arguments must be numeric");
            }
            catch (System.InvalidCastException)
            {
                throw new YarrException("NumberEql: arguments must be numeric");
            }
        }

        internal static Type TypeOf(Object arg)
        {
            if (arg == null)
                throw new YarrException("TypeOf: Unable to determine type of null instance.");

            Type t = arg.GetType();

            if (t == typeof(CastInstance))
                return ((CastInstance)arg).CastType;

            return t;
        }

        internal static Object ValueOf (Object arg)
        {
            if (arg == null)
                return null;

            if (arg is CastInstance)
                return ((CastInstance)arg).Value;

            return arg;
        }

        #endregion


        #region Utility methods

        internal static Object ParseString(string expression, Environment environment)
        {
            if (String.IsNullOrEmpty(expression))
                return null;

            if (environment == null)
                throw new YarrException("ParseString: Environment is null");

            using (StringReader sr = new StringReader(expression))
            {
                ReadTable readTable = (ReadTable)environment.GetValue(Symbol.READTABLE);
                return Reader.Read(sr, readTable, environment);
            }
        }

        internal static Cons ParseDotNetReference(Symbol callName, Object cdr, 
                                                  bool addCall, Environment env)
        {
            if (String.IsNullOrEmpty(callName.Name))
                return null;

            int dot;
            Cons retVal = null;

            if ((dot = callName.Name.LastIndexOf('.')) != -1)
            {
                Symbol methodName = Symbol.FromName(String.Empty, callName.Name.Substring(dot + 1));
                string instName = callName.Name.Substring(0, dot);

                if (instName.IndexOf('.') >= 0 &&
                        TypeCache.FindType(instName) == null &&
                        env.GetValue(instName) == null)
                    retVal = new Cons(methodName,
                                      new Cons(ParseDotNetReference(Symbol.FromName(callName.PackageName, instName), 
                                                                    null, true, env), 
                                               cdr));
                else
                    retVal = new Cons(methodName,
                                      new Cons(Symbol.FromName(callName.PackageName, instName), cdr));
            }
            else
                retVal = new Cons(Symbol.Keyword(callName.Name), cdr);

            return addCall ? new Cons(Symbol.CALL, retVal) : retVal;
        }

        internal static void ProcessArguments(Cons argumentNameList, Cons argumentList, 
                                                Environment localEnvironment) 
		{
            if (localEnvironment == null)
                throw new YarrException("ProcessArguments: Local environment is null");

            while (argumentNameList != null) 
			{
                // Get the name for the closure's parameter.  Then check to see if it's a keyword, if it is then
                // process the keyword.  Otherwise set up that parameter in the closure's enviroment with the
                // caller specified value.

				Object argumentName = argumentNameList.Car();

                if (argumentName is Symbol)
                {
                    switch (((Symbol)argumentName).Name)
                    {
                        case "&rest":
                            argumentName = argumentNameList.Cadr();
                            localEnvironment.AssignLocal((Symbol)argumentName, argumentList);
                            argumentNameList = null;
                            argumentList = null;
                            break;

                        case "&optional":
                            ProcessOptionalArguments((Cons)argumentNameList.Cdr(), argumentList, localEnvironment);
                            argumentNameList = null;
                            argumentList = null;
                            break;

                        case "&key":
                            ProcessKeyArguments((Cons)argumentNameList.Cdr(), argumentList, localEnvironment);
                            argumentNameList = null;
                            argumentList = null;
                            break;

                        default:
                            if (argumentList == null)
                                throw new YarrException("ProcessArguments: Not enough parameters given");

                            localEnvironment.AssignLocal((Symbol)argumentName, argumentList.Car());
                            break;
                    }
                }

                // a type is specified for the parameter
                else if (argumentName is Cons)
                {
                    Symbol name = (Symbol)((Cons)argumentName).Car();

                    Type t = (Type)Runtime.Eval(((Cons)argumentName).Cadr(), localEnvironment);
                    if (t == null)
                        throw new YarrException(String.Format(CultureInfo.CurrentCulture, 
                                                        "ProcessArguments: {0} is not a type", 
                                                        ((Cons)argumentName).Cadr().ToString()));

                    Object arg = argumentList.Car();
                    localEnvironment.Declare(name, t, arg);
                }

                if (argumentList != null)
                    argumentList = (Cons)argumentList.Cdr();

                if (argumentNameList != null)
                    argumentNameList = (Cons)argumentNameList.Cdr();
            }


            // Looks like the caller has supplied more parameters than the closure can use.
            if (argumentList != null)
                throw new YarrException("ProcessArguments: Too many parameters given");
		}

        private static void ProcessKeyArguments(Cons argumentNameList, Cons argumentList,
                                                Environment localEnvironment)
        {
            // used to look up parameter types
            int dictCapacity = argumentNameList.Length; // worst case
            Dictionary<String, Symbol> argumentSymbols = new Dictionary<string, Symbol>(dictCapacity);
            Dictionary<String, Type> argumentTypes = new Dictionary<String, Type>(dictCapacity);
            Dictionary<String, Object> arguments = new Dictionary<String, Object>(dictCapacity);
            Symbol restName = null;

            while (argumentNameList != null)
            {
                Symbol argumentName = null;
                Type argumentType = null;
                Object argumentValue = null;

                // See if &rest parameter is specified
                if (argumentNameList.Car() == Symbol.REST)
                {
                    if (restName != null)
                        throw new YarrException("ProcessArguments: Can only specify one &rest parameter");

                    restName = (Symbol)argumentNameList.Cadr();
                    argumentNameList = (Cons)argumentNameList.Cddr();
                    continue;
                }

                // Get the name of the argument. It can either be just the name or, it can be
                // it's own Cons with the name and an expression for the default value.

                if (argumentNameList.Car() is Cons)
                {
                    // It is a Cons, so extract the name and the default value.  Because the default can be
                    // any expression, we need to evaluate the value every time the function is called.

                    Cons argDef = (Cons)argumentNameList.Car();

                    // Type specification along with default value
                    if (argDef.Car() is Cons)
                    {
                        argumentName = (Symbol)argDef.Caar();
                        argumentType = Runtime.Eval(argDef.Cadar(), localEnvironment) as Type;
                        if (argumentType == null)
                            throw new YarrException(String.Format(CultureInfo.CurrentCulture, 
                                        "ProcessArguments: No such type {0}", argDef.Cadar().ToString()));
                    }
                    else
                        argumentName = (Symbol)argDef.Car();

                    argumentValue = argDef.Second();
                }
                else
                    argumentName = (Symbol)argumentNameList.Car();

                if (arguments.ContainsKey(argumentName.Name))
                    throw new YarrException(String.Format(CultureInfo.CurrentCulture, 
                                "ProcessArgumens: Keyword {0} defined more than once", argumentName));

                argumentSymbols[argumentName.Name] = argumentName;
                arguments[argumentName.Name] = argumentValue;
                if (argumentType != null)
                    argumentTypes[argumentName.Name] = argumentType;

                argumentNameList = (Cons)argumentNameList.Cdr();
            }


            // Now that the parameters and their defaults have been added to the environment we can now
            // process the supplied arguments.

            while (argumentList != null)
            {
                // Because these are keyed parameters, the caller needs to specify the name of each
                // parameter.

                if (!(argumentList.Car() is Symbol))
                    throw new YarrException("ProcessArguments: Key parameters must be specified by name");

                // Grab the current parameter and the value associated with it.  Then make sure that this
                // is a keyword.

                Symbol keywordName = (Symbol)argumentList.Car();
                object argumentValue = argumentList.Cadr();

                if (keywordName.PackageName != Package.KEYWORD)
                    throw new YarrException(String.Format(CultureInfo.CurrentCulture, 
                                "ProcessArguments: {0} is not a valid keyword", keywordName));

                // Now that we know they supplied a keyword, create a symbol out of it and make sure that
                // it exists.

                if (!arguments.ContainsKey(keywordName.Name))
                    throw new YarrException(
                        String.Format(CultureInfo.CurrentCulture, 
                            "ProcessArguments: :{0} is not a valid keyword, or appears more than once", 
                            keywordName.Name));

                Type argumentType = null;
                if (argumentTypes.ContainsKey(keywordName.Name))
                    argumentType = argumentTypes[keywordName.Name];

                if (argumentType != null)
                    localEnvironment.Declare(argumentSymbols[keywordName.Name], argumentType, argumentValue);
                else
                    localEnvironment.AssignLocal(argumentSymbols[keywordName.Name], argumentValue);

                arguments.Remove(keywordName.Name);
                argumentList = (Cons)argumentList.Cddr();
            }

            if (restName != null)
                localEnvironment.AssignLocal(restName, argumentList);

            // Any remaining arguments get the default value

            foreach (String arg in arguments.Keys)
            {
                Object argValue = Runtime.Eval(arguments[arg], localEnvironment);
                if (argValue is CastInstance)
                    argValue = ((CastInstance)argValue).Value;

                if (argumentTypes.ContainsKey(arg))
                {
                    Type t = argumentTypes[arg];

                    if (argValue == null)
                    {
                        if (Type.GetTypeCode(t) == TypeCode.Object)
                            argValue = null;
                        else
                            argValue = Runtime.MakeInstance(t, null);
                    }

                    if (argValue != null && !Primitives.IsTypeOf(t, argValue))
                    {
                        if (!Conversions.TryImplicitConversion(argValue, t, out argValue))
                            throw new YarrException(
                                String.Format(CultureInfo.CurrentCulture,
                                        "ProcessArguments: Expecting argument of type {0} for parameter {1}",
                                        t.ToString(), arg));
                    }
                }

                localEnvironment.AssignLocal(argumentSymbols[arg], argValue);
            }
        }

        private static void ProcessOptionalArguments(Cons argumentNameList, Cons argumentList,
                                                Environment localEnvironment)
        {
            // We need to add all the arguments to the closure's environment.

            while (argumentNameList != null)
            {
                Symbol argumentName = null;
                Type argumentType = null;
                object argumentValue = null;

                bool supplied = false; // if true, the argument has been supplied

                // We need to get the name of the argument, it can either be just the name or, it can be
                // it's own Cons with the name and an expression for the default value.

                if (argumentNameList.Car() is Cons)
                {
                    Cons arg = (Cons)argumentNameList.Car();
                    if (arg.Car() is Cons)
                    {
                        argumentName = (Symbol)arg.Caar();
                        argumentType = Runtime.Eval(arg.Cadar(), localEnvironment) as Type;
                        if (argumentType == null)
                            throw new YarrException(String.Format(CultureInfo.CurrentCulture,
                                        "ProcessArguments: No such type {0}", arg.Cadar().ToString()));

                        argumentValue = argumentNameList.Cadar();
                    }
                    else
                    {
                        argumentName = (Symbol)arg.Car();
                        argumentValue = arg.Cadr();
                    }
                }
                else
                {
                    argumentName = (Symbol)argumentNameList.Car();
                    if (argumentName == Symbol.REST)
                    {
                        localEnvironment.AssignLocal((Symbol)argumentNameList.Cadr(), argumentList);
                        return;
                    }
                }


                // Now, if the caller has specified a value for this argument, get it now.

                if (argumentList != null)
                {
                    argumentValue = argumentList.Car();
                    supplied = true;
                    argumentList = (Cons)argumentList.Cdr();
                }

                else if (argumentType != null && argumentValue == null)
                {
                    supplied = true;
                    if (Type.GetTypeCode(argumentType) == TypeCode.Object)
                        argumentValue = null;
                    else
                        argumentValue = Runtime.MakeInstance(argumentType, null);
                }


                // Finally add the parameter to the closure's list and then move onto the next argument.
                // Because the default can be any expression, we need to evaluate the value every time the
                // function is called.

                if (!supplied)
                    argumentValue = Runtime.Eval(argumentValue, localEnvironment);

                if (argumentType != null)
                    localEnvironment.Declare(argumentName, argumentType, argumentValue);
                else
                    localEnvironment.AssignLocal(argumentName, argumentValue);

                if (argumentNameList != null)
                    argumentNameList = (Cons)argumentNameList.Cdr();
            }

            // Looks like the caller has supplied more parameters than the closure can use.

            if (argumentList != null)
                throw new YarrException("ProcessArguments: Too many parameters given");
        }


        internal static void ProcessMacroArguments(Cons argumentNameList, Cons argumentList, 
                                                Environment localEnvironment)
        {
            if (localEnvironment == null)
                throw new YarrException("ProcessMacroArguments: Local environment is null");

            while (argumentNameList != null)
            {
                // Get the name for the macro's parameter.  Then check to see if it's a keyword, if it is then
                // process the keyword.  Otherwise set up that parameter in the macro's enviroment with the
                // caller specified value.

                Object argumentName = argumentNameList.Car();

                if (argumentName is Symbol)
                {
                    switch (((Symbol)argumentName).Name)
                    {
                        case "&body":
                        case "&rest":
                            argumentName = argumentNameList.Cadr();
                            localEnvironment.AssignLocal((Symbol)argumentName, argumentList);
                            argumentNameList = null;
                            argumentList = null;
                            break;

                        case "&optional":
                            ProcessOptionalMacroArguments((Cons)argumentNameList.Cdr(), argumentList, localEnvironment);
                            argumentNameList = null;
                            argumentList = null;
                            break;

                        case "&key":
                            ProcessKeyMacroArguments((Cons)argumentNameList.Cdr(), argumentList, localEnvironment);
                            argumentNameList = null;
                            argumentList = null;
                            break;

                        default:
                            if (argumentList == null)
                                throw new YarrException("ProcessMacroArguments: Not enough parameters given");

                            localEnvironment.AssignLocal((Symbol)argumentName, argumentList.Car());
                            break;
                    }
                }

                // a destrucuted parameter
                else if (argumentName is Cons)
                    DestructureParameter(argumentName, argumentList.Car(), localEnvironment);

                if (argumentList != null)
                    argumentList = (Cons)argumentList.Cdr();
                if (argumentNameList != null)
                    argumentNameList = (Cons)argumentNameList.Cdr();
            }


            // The caller has supplied more parameters than the closure can use.

            if (argumentList != null)
                throw new YarrException("ProcessMacroArguments: Too many parameters given");
        }

        private static void ProcessOptionalMacroArguments(Cons argumentNameList, Cons argumentList, 
                                                Environment localEnvironment)
        {
            // We need to add all the arguments to the closure's environment.

            while (argumentNameList != null)
            {
                Symbol argumentName = null;
                object argumentValue = null;

                // Get the name of the argument. It can either be just the name or, it can be
                // it's own Cons with the name and an expression for the default value.

                if (argumentNameList.Car() is Cons)
                {
                    Cons arg = (Cons)argumentNameList.Car();
                    argumentName = (Symbol)arg.Car();
                    argumentValue = arg.Cadr();
                }
                else
                {
                    argumentName = (Symbol)argumentNameList.Car();
                    if (argumentName == Symbol.REST || argumentName == Symbol.BODY)
                    {
                        localEnvironment.AssignLocal((Symbol)argumentNameList.Cadr(), argumentList);
                        return;
                    }
                }

                localEnvironment.AssignLocal(argumentName, 
                                             (argumentList == null) ? 
                                                    Runtime.Eval(argumentValue, localEnvironment)
                                                                  : argumentList.Car());

                if (argumentList != null)
                    argumentList = (Cons)argumentList.Cdr();
                if (argumentNameList != null)
                    argumentNameList = (Cons)argumentNameList.Cdr();
            }

            // The caller has supplied more parameters than the macro can use.

            if (argumentList != null)
                throw new YarrException("ProcessMacroArguments: Too many parameters given");
        }

        private static void ProcessKeyMacroArguments(Cons argumentNameList, Cons argumentList, 
                                                Environment localEnvironment)
        {
            // Make sure that all of the defined key arguments are
            // inserted into the local environment with their defaults.
            int dictCapacity = argumentNameList.Length; // worst case
            Dictionary<String, Symbol> argumentSymbols = new Dictionary<String, Symbol>(dictCapacity);
            Dictionary<String, Object> arguments = new Dictionary<String, Object>(dictCapacity);

            Symbol restName = null;

            while (argumentNameList != null)
            {
                Symbol argumentName = null;
                object argumentValue = null;

                // See if &rest parameter is specified
                if (argumentNameList.Car() == Symbol.REST || argumentNameList.Car() == Symbol.BODY)
                {
                    if (restName != null)
                        throw new YarrException("ProcessMacroArguments: Can only specify one &rest parameter");

                    restName = (Symbol)argumentNameList.Cadr();
                    argumentNameList = (Cons)argumentNameList.Cddr();
                    continue;
                }

                // We need to get the name of the argument, it can either be just the name or, it can be
                // it's own Cons with the name and an expression for the default value.
  
                if (argumentNameList.Car() is Cons)
                {
                    // It is a Cons, so extract the name and the default value.  Because the default can be
                    // any expression, we need to evaluate the value every time the function is called.
  
                    argumentName = (Symbol)argumentNameList.Caar();
                    argumentValue = argumentNameList.Cadar();
                }
                else
                    argumentName = (Symbol)argumentNameList.Car();
  
                argumentSymbols[argumentName.Name] = argumentName;
                arguments[argumentName.Name] = argumentValue;
                argumentNameList = (Cons)argumentNameList.Cdr();
            }
   
            // Now that the parameters and their defaults have been identified,
            // we can now process the supplied arguments.
  
            while (argumentList != null)
            {
                // Because these are keyed parameters, the caller needs to
                // specify the name of each parameter.

                if (!(argumentList.Car() is Symbol))
                    throw new YarrException("ProcessMacroArguments: Key parameters must be specified by name.");

                // Grab the current parameter and the value associated with it.
                // Then make sure that this is a keyword.

                Symbol keywordName = (Symbol)argumentList.Car();
                object argumentValue = argumentList.Cadr();

                if (keywordName.PackageName != Package.KEYWORD)
                    throw new YarrException(String.Format(CultureInfo.CurrentCulture,
                                        "ProcessMacroArguments: {0} is not a valid keyword.", keywordName));
 
                if (!arguments.ContainsKey(keywordName.Name))
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture,
                                "ProcessMacroArguments: :{0} is not a defined keyword", keywordName.Name));

                // Update the parameter with the value that the user specified and then move
                // and then move on to the next argument in the list.

                localEnvironment.AssignLocal(argumentSymbols[keywordName.Name], argumentValue);
                arguments.Remove(keywordName.Name);

                argumentList = (Cons)argumentList.Cddr();
            }

            if (restName != null)
                localEnvironment.AssignLocal(restName, argumentList);

            // Any remaining arguments get their default values

            foreach (string arg in arguments.Keys)
                localEnvironment.AssignLocal(argumentSymbols[arg], Runtime.Eval(arguments[arg], localEnvironment));
        }

        private static void DestructureParameter (Object argName, Object argList, 
                                                Environment localEnvironment)
        {
            if (argName == null)
            {
                if (argList == null)
                    return;
                else
                    throw new YarrException("DestructureParameter: Argument has incompatible structure");
            }

            if (argName is Symbol)
                localEnvironment.AssignLocal((Symbol)argName, argList);

            else if (argName is Cons)
            {
                if (argList == null  ||  !(argList is Cons))
                    throw new YarrException(
                        String.Format(CultureInfo.CurrentCulture,
                            "DestructureParameter: Argument for {0} has incompatible structure", argName));

                DestructureParameter(((Cons)argName).Car(), ((Cons)argList).Car(), localEnvironment);
                DestructureParameter(((Cons)argName).Cdr(), ((Cons)argList).Cdr(), localEnvironment);
            }
        }
        
        #endregion
    }


    /// <summary>
    /// An up-cast object
    /// </summary>
    internal class CastInstance
    {
        private Type castType;
        private Object value;

        public Type CastType { get { return castType; } }
        public Object Value { get { return value; } }

        public CastInstance(Type t, Object o)
        {
            castType = t;
            value = o;
        }

        public override int GetHashCode()
        {
            return (value == null) ? base.GetHashCode() : value.GetHashCode();
        }

        public override string ToString()
        {
            return (value == null) ? "null" : value.ToString();
        }
    }
}
